//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2020-10-30     ZhangYihua   first version
//
// Description  : 
//################################################################################
//
//         2^(-k)
//          |            /---<----\
//  in_dat  V            |        |       out_dat
//  ------>(*)-(st)-[D]-(+)-(s)->[D]--(t)--------->
//                              acc_ff
//
// acc = acc + in_dat*2^(-k); out_dat = round(acc)
module p_acc #(   //proportional accumulator 
parameter           IN_DW                   = 10,
parameter           IN_FW                   = 9,
parameter           OUT_DW                  = 12,
parameter           OUT_FW                  = 9,    // must OUT_FW<=ACC_FW
parameter           ACC_FW                  = 24,   // must ACC_FW<=IN_FW+K_MAX
parameter           K_MAX                   = 15,   // must K_MAX>=K_MIN
parameter           K_MIN                   = 8,

// the following parameters are calculated automatically
parameter           KW                      = $clog2(K_MAX+1) // range [K_MIN:K_MAX]
) ( 
input                                       rst_n,
input                                       clk,
input                                       cke,

input                                       in_vld,
input       signed  [IN_DW-1:0]             in_dat,     // s( IN_DW,  IN_FW)
output      signed  [OUT_DW-1:0]            out_dat,    // s(OUT_DW, OUT_FW), convert it to unsinged by flop the highest bit

input               [KW-1:0]                cfg_k,          // range [K_MIN:K_MAX], gain = 2^(-k)

input                                       cfg_acc_clr,    // clear acc to cfg_ini
input                                       cfg_acc_frz,    // freeze acc, but cfg_frz is lower priority than cfg_clr
input       signed  [OUT_DW-1:0]            cfg_acc_ini,    // s(OUT_DW, OUT_FW), initial value
output      signed  [OUT_DW-1:0]            sts_acc_rtv     // s(OUT_DW, OUT_FW), real-time value
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          IN_IW                   = IN_DW-IN_FW;      // IN_IW<=0 is legal
localparam          OUT_IW                  = OUT_DW-OUT_FW;    // OUT_IW<=0 is legal
localparam          ACC_IW                  = OUT_IW;
localparam          ACC_DW                  = ACC_IW+ACC_FW;
localparam          INC_IW                  = MIN_F(IN_IW-K_MIN, ACC_IW);
localparam          INC_FW                  = ACC_FW;
localparam          INC_DW                  = INC_IW+INC_FW;

wire        signed  [IN_DW+K_MAX-K_MIN-1:0] gain_dat;
wire                                        cke_inc_dat;
wire        signed  [INC_DW-1:0]            inc_dat;
wire                                        inc_over_nc;
reg                                         inc_vld;
wire        signed  [ACC_DW+1-1:0]          acc_c;
wire        signed  [ACC_DW-1:0]            acc_s;
wire                                        acc_over_nc;
reg         signed  [ACC_DW-1:0]            cfg_ini_e;
reg         signed  [ACC_DW-1:0]            acc_ff;
wire                                        out_over_nc;

//################################################################################
// main
//################################################################################

s_gain_nkpo2 #(  // gain by negative k power of 2
        .DW                             (IN_DW+K_MAX                    ),	// data width
        .K_MAX                          (K_MAX                          ),	// Maximum of cfg_k, K_MAX>=1
        .K_MIN                          (K_MIN                          ) 	// Minimum of cfg_k
) u_gain_kp ( 
        .id                             ({in_dat, {K_MAX{1'b0}}}        ),  // s(IN_DW+K_MAX,      IN_FW+K_MAX)
        .od                             (gain_dat                       ),  // s(IN_DW+K_MAX-K_MIN,IN_FW+K_MAX)

        .cfg_k                          (cfg_k                          )	// range [K_MIN:K_MAX], gain 
);

assign cke_inc_dat = cke & in_vld;
s_sat_tru_reg #(     // range [-(2^(IDW-1))/(2^IFW):(2^(IDW-1)-1)/(2^IFW)]
        .IDW                            (IN_DW+K_MAX-K_MIN              ),	// input data width
        .IFW                            (IN_FW+K_MAX                    ),	// input fractional width
        .ODW                            (INC_DW                         ),	// output data width
        .OFW                            (INC_FW                         ),	// output fractional width
        .TRU_MODE                       ("FLOOR"                        )	// discade fractional bits directly for less area and higher Fmax
) u_inc_dat_tru ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke_inc_dat                    ),

        .id                             (gain_dat                       ),	// s(IDW, IFW), the MSB is sign
        .od                             (inc_dat                        ),	// s(ODW, OFW), the MSB is sign
        .over                           (inc_over_nc                    )
);

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        inc_vld <=`U_DLY 1'b0;
    end else if (cke==1'b1) begin
        inc_vld <=`U_DLY in_vld;
    end else
        ;
end

// acc = acc+in_dat; out_dat = truncate(acc);
acc #(
        .IN_DW                          (INC_DW                         ),	// IN_DW-IN_FW<=OUT_DW-OUT_FW
        .IN_FW                          (INC_FW                         ),
        .OUT_DW                         (OUT_DW                         ),
        .OUT_FW                         (OUT_FW                         )	// must OUT_FW<=IN_FW
) u_acc ( 
        .rst_n                          (rst_n                          ),
        .clk                            (clk                            ),
        .cke                            (cke                            ),

        .in_vld                         (inc_vld                        ),
        .in_dat                         (inc_dat                        ),	// s( IN_DW,  IN_FW)
        .out_dat                        (out_dat                        ),	// s(OUT_DW, OUT_FW)

        .cfg_acc_clr                    (cfg_acc_clr                    ),	// clear acc to cfg_ini
        .cfg_acc_frz                    (cfg_acc_frz                    ),	// freeze acc, but cfg_frz is lower priority than cfg_clr
        .cfg_acc_ini                    (cfg_acc_ini                    ),	// s(OUT_DW, OUT_FW)
        .sts_acc_rtv                    (sts_acc_rtv                    )	// s(OUT_DW, OUT_FW), real-time value
);

`include "func_param.v" // refer to MIN_F()

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off

initial begin
    if (OUT_FW>ACC_FW) begin
        $error("out fraction width is bigger than ACC fraction width.");
        $stop;
    end

    if (ACC_FW>IN_FW+K_MAX) begin
        $error("ACC fraction width is bigger than IN_FW+K_MAX.");
        $stop;
    end
end

// synopsys translate_on
`endif

endmodule
